package vip.liux.backend.application.serviceImpl.pay;


import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.liux.application.dots.PagedResultDto;
import vip.liux.backend.application.service.pay.*;
import vip.liux.backend.application.service.pay.dto.order.PayOrderDto;
import vip.liux.backend.application.service.pay.dto.refund.PayRefundDetailsDto;
import vip.liux.backend.application.service.pay.dto.wallet.PayWalletDto;
import vip.liux.backend.application.service.pay.dto.wallet.PayWalletQueryDto;
import vip.liux.backend.application.service.pay.dto.wallet.PayWalletTransactionDto;
import vip.liux.backend.application.service.pay.dto.wallet.WalletTransactionCreateDto;
import vip.liux.contracts.models.pay.PayWallet;
import vip.liux.contracts.repositories.pay.PayWalletRepository;
import vip.liux.contracts.shared.enums.pay.PayWalletBizType;
import vip.liux.lock.LockTemplate;

import java.math.BigDecimal;

import static vip.liux.application.PageKit.toPage;
import static vip.liux.application.PageKit.toPaged;
import static vip.liux.contracts.shared.enums.ErrorCode.*;
import static vip.liux.contracts.shared.enums.pay.PayWalletBizType.PAYMENT;
import static vip.liux.contracts.shared.enums.pay.PayWalletBizType.PAYMENT_REFUND;
import static vip.liux.contracts.shared.util.ServiceExceptionUtil.exception;


/**
 * 钱包 Service 实现类
 *
 * @author jason
 */
@Service
@Slf4j
public class PayWalletServiceImpl implements PayWalletService {

    /**
     * 通知超时时间，单位：毫秒
     */
    // public static final long UPDATE_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS;

    @Resource
    private PayWalletRepository walletRepo;
    @Resource
    private LockTemplate lockTemplate;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private PayWalletTransactionService walletTransactionService;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private PayOrderService orderService;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private PayRefundService refundService;

    @Override
    public PayWalletDto getOrCreateWallet(String uid) {
        PayWallet wallet = walletRepo.findByUid(uid).orElseGet(() -> {
            PayWallet entity = new PayWallet()
                    .setUid(uid)
                    .setBalance(BigDecimal.ZERO)
                    .setFreezePrice(BigDecimal.ZERO)
                    .setTotalExpense(BigDecimal.ZERO)
                    .setTotalRecharge(BigDecimal.ZERO);
            return walletRepo.save(entity);
        });
        return AutoMapperProfile.INSTANCE.convert(wallet);
    }

    @Override
    public PayWalletDto getWallet(Long walletId) {
        return AutoMapperProfile.INSTANCE.convert(walletRepo.findById(walletId).orElseThrow());
    }

    @Override
    public PagedResultDto<PayWalletDto> getWalletPage(PayWalletQueryDto input) {
        return toPaged(walletRepo.findPage(input.getUid(), toPage(input)).map(AutoMapperProfile.INSTANCE::convert));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayWalletTransactionDto orderPay(Long walletId, String outTradeNo, BigDecimal price) {
        // 1. 判断支付交易拓展单是否存
        PayOrderDto payOrder = orderService.getByOrderNo(outTradeNo);
        if (payOrder == null) {
            throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
        }
        PayWallet wallet = walletRepo.findById(walletId).orElseThrow();
        // 2. 扣减余额
        return reduceWalletBalance(wallet.getId(), payOrder.getId(), PAYMENT, price);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayWalletTransactionDto orderRefund(String outRefundNo, BigDecimal refundPrice, String reason) {
        // 1.1 判断退款单是否存在
        PayRefundDetailsDto payRefund = refundService.getRefundByRefundNo(outRefundNo);
        if (payRefund == null) {
            throw exception(REFUND_NOT_FOUND);
        }
        // 1.2 校验是否可以退款
        Long walletId = validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo());
        PayWallet wallet = walletRepo.findById(walletId).orElseThrow();

        // 2. 增加余额
        return addWalletBalance(wallet.getId(), String.valueOf(payRefund.getId()), PAYMENT_REFUND, refundPrice);
    }

    /**
     * 校验是否能退款
     *
     * @param refundId    支付退款单 id
     * @param walletPayNo 钱包支付 no
     */
    private Long validateWalletCanRefund(Long refundId, String walletPayNo) {
        // 1. 校验钱包支付交易存在
        PayWalletTransactionDto walletTransaction = walletTransactionService.getWalletTransactionBySeqNo(walletPayNo);
        if (walletTransaction == null) {
            throw exception(WALLET_TRANSACTION_NOT_FOUND);
        }
        // 2. 校验退款是否存在
        PayWalletTransactionDto refundTransaction = walletTransactionService.getWalletTransaction(
                String.valueOf(refundId), PAYMENT_REFUND);
        if (refundTransaction != null) {
            throw exception(WALLET_REFUND_EXIST);
        }
        return walletTransaction.getWalletId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @SneakyThrows
    public PayWalletTransactionDto reduceWalletBalance(Long walletId, Long bizId,
                                                       PayWalletBizType bizType, BigDecimal price) {
        // 1. 获取钱包
        PayWalletDto payWallet = getWallet(walletId);
        if (payWallet == null) {
            log.error("[reduceWalletBalance][用户钱包({})不存在]", walletId);
            throw exception(WALLET_NOT_FOUND);
        }

        // 2. 加锁，更新钱包余额（目的：避免钱包流水的并发更新时，余额变化不连贯）
        return lockTemplate.execute(String.valueOf(walletId), lockKey -> {
            // 2. 扣除余额
            int updateCounts;
            switch (bizType) {
                case PAYMENT: {
                    updateCounts = walletRepo.updateWhenConsumption(payWallet.getId(), price);
                    break;
                }
                case RECHARGE_REFUND: {
                    updateCounts = walletRepo.updateWhenRechargeRefund(payWallet.getId(), price);
                    break;
                }
                default: {
                    // TODO 其它类型待实现
                    throw new UnsupportedOperationException("待实现");
                }
            }
            if (updateCounts == 0) {
                throw exception(WALLET_BALANCE_NOT_ENOUGH);
            }

            // 3. 生成钱包流水
            BigDecimal afterBalance = payWallet.getBalance().subtract(price);
            WalletTransactionCreateDto bo = new WalletTransactionCreateDto()
                    .setWalletId(payWallet.getId())
                    .setPrice(price.negate())
                    .setBalance(afterBalance)
                    .setBizId(String.valueOf(bizId))
                    .setBizType(bizType)
                    .setTitle(bizType.getDesc());
            return walletTransactionService.createWalletTransaction(bo);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @SneakyThrows
    public PayWalletTransactionDto addWalletBalance(Long walletId, String bizId,
                                                    PayWalletBizType bizType, BigDecimal price) {
        // 1. 获取钱包
        PayWalletDto payWallet = getWallet(walletId);
        if (payWallet == null) {
            log.error("[addWalletBalance][用户钱包({})不存在]", walletId);
            throw exception(WALLET_NOT_FOUND);
        }

        // 2. 加锁，更新钱包余额（目的：避免钱包流水的并发更新时，余额变化不连贯）
        return lockTemplate.execute(String.valueOf(walletId), lockKey -> {
            // 3. 更新钱包金额
            switch (bizType) {
                case PAYMENT_REFUND: { // 退款更新
                    walletRepo.updateWhenConsumptionRefund(payWallet.getId(), price);
                    break;
                }
                case RECHARGE: { // 充值更新
                    walletRepo.updateWhenRecharge(payWallet.getId(), price);
                    break;
                }
                case UPDATE_BALANCE: // 更新余额
                case TRANSFER: // 分佣提现
                    walletRepo.updateWhenAdd(payWallet.getId(), price);
                    break;
                default: {
                    throw new UnsupportedOperationException("待实现：" + bizType);
                }
            }

            // 4. 生成钱包流水
            WalletTransactionCreateDto transactionCreateReqBO = new WalletTransactionCreateDto()
                    .setWalletId(payWallet.getId())
                    .setPrice(price)
                    .setBalance(payWallet.getBalance().add(price))
                    .setBizId(bizId)
                    .setBizType(bizType)
                    .setTitle(bizType.getDesc());
            return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
        });
    }

    @Override
    public void freezePrice(Long id, BigDecimal price) {
        int updateCounts = walletRepo.freezePrice(id, price);
        if (updateCounts == 0) {
            throw exception(WALLET_BALANCE_NOT_ENOUGH);
        }
    }

    @Override
    public void unfreezePrice(Long id, BigDecimal price) {
        int updateCounts = walletRepo.unFreezePrice(id, price);
        if (updateCounts == 0) {
            throw exception(WALLET_FREEZE_PRICE_NOT_ENOUGH);
        }
    }
}
